package com.gary.shiro.aspect;

import com.gary.shiro.annotation.Cache;
import com.gary.shiro.util.EncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * cache 注解解析
 * @author liliang
 * @date 2021/12/28
 **/
@Slf4j
@Aspect
@Component
public class CacheAspect {

    @Value("${cache.prefix:arch.gmp.cache:}")
    private String cachePrefix;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //切点 包含Cache注解的方法
    @Pointcut("@annotation(com.gary.shiro.annotation.Cache)")
    public void cache() {
    }

    //环绕通知
    @Around("cache()")
    public Object handle(ProceedingJoinPoint point) throws Throwable {
        String uid = null;
        try {
            //请求唯一标识
            uid = generateUid(point);
            Object result = point.proceed();
            //存储缓存 result必须能被json 序列化 否则会报错 缓存只会保留5分钟
            writeCache(result, uid);
            return result;
        }catch (Exception e) {
            log.error("方法执行异常 uid = {} ", uid, e);
            return readCache(uid);
        }
    }

    private void writeCache(Object result, String uid) {
        String key = getCacheKey(uid);
        redisTemplate.opsForValue().set(key, result, 300, TimeUnit.SECONDS);
    }

    private String getCacheKey(String uid) {
        return cachePrefix + uid;
    }

    private Object readCache(String uid) {
        String key = getCacheKey(uid);
        return redisTemplate.opsForValue().get(key);
    }

    private String generateUid(ProceedingJoinPoint point) throws Exception {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Cache cache = signature.getMethod().getAnnotation(Cache.class);
        String uid = cache.uid();

        //没有配置uid 默认以方法名 和 方法参数 md5 的key作为唯一key
        if (StringUtils.isEmpty(uid)) {
            String methodName = signature.getMethod().getName();
            Object[] args = point.getArgs();
            StringBuilder sb = new StringBuilder(methodName);
            if (args != null) {
                for (Object val : args) {
                    sb.append("&").append(val.hashCode());
                }
            }
            return EncryptUtil.md5Encrypt(sb.toString());
        }

        return uid;
    }
}